/**
 * CS202 - Spring 2009 
 * Professor: Dr. Michael Peterson
 * Student: Deylo Woo (dwoo711@gmail.com)
 */
package battleship;

import java.util.*;

/**
 * 
 * Constructor
 */
public class GameManager {

    private Grid _grid;
    private LinkedList<Ship> _ships = new LinkedList<Ship>();
    private static Scanner _kb = new Scanner(System.in);

    /**
     * Constructor, first one
     */
    public GameManager() {
    }

    /**
     * Print Grid display all ships
     */
    public void PrintGrid() {
        _grid.PrintGrid();
    }

    /**
     * Print the Grid for end Game, display all ships
     * @param _ships: holds all current ships
     */
    public void PrintGridEndGame(LinkedList<Ship> _ships) {
        _grid.PrintGridEndGame(_ships);
    }

    /**
     * Print the grid for Game, hide all ships
     * @param _ships
     */
    public void PrintGrid4Game(LinkedList<Ship> _ships) {
        _grid.PrintGrid4Game(_ships);
    }

    /**
     * Generate the grid
     * @param xSize: initial x size
     * @param ySize: initial y size
     */
    public void GenerateGrid(Integer xSize, Integer ySize) {
        _grid = new Grid(xSize, ySize);
    }

    /**
     * Create new ships and add to the collection
     * @param hitPoints: damage points, I may not use it later, instead I may use hitpointvalues on Position object
     * @param size: number of dots
     * @param title: name of the ship
     */
    public void GenerateShip(Integer hitPoints, Integer size, String title) {
        LinkedList<Position> positions = new LinkedList<Position>();
        for (Integer x = 1; x <= size; x++) {
            positions.add(new Position("1", 0, 0));
        }
        _ships.add(new Ship(title, hitPoints, Status.ok, positions));
    }

    /**
     * after the collection of ships has been create, then will place the ships on the Grid randomly.
     */
    public void PlaceShipsOnGrid() {
        //generate Rand num 
        //Integer initPos = (int)((Math.random() * 100) + 1);
        for (Ship _ship : _ships) {
            boolean isShipPlaced = false;
            int _randTry = 0;
            do {
                Integer xInitPos = (int) (Math.random() * 10); //rand from 0 - 9
                Integer yInitPos = (int) (Math.random() * 10); //rand from 0 - 9
                //Is initial pos available
                _randTry++;
                if (IsPositionAvailable(xInitPos, yInitPos)) {
                    //Rand for direction 1= left 2=up 3=right 4=down
                    int[] _dirs = new int[]{1, 2, 3, 4};
                    do {
                        Integer dir = (int) ((Math.random() * 4) + 1);
                        if (_dirs[dir - 1] > 0) {
                            _dirs[dir - 1] = 0;
                            isShipPlaced = true;
                            if (dir == 3) {//dir = right
                                //Check if all pos are available and assign
                                isShipPlaced = AssignRight(xInitPos, _ship, yInitPos, isShipPlaced);
                            //isShipPlaced = false;
                            } else if (dir == 4) {// dir = down
                                //Check if all pos are available and assign
                                isShipPlaced = AssignDown(yInitPos, _ship, xInitPos, isShipPlaced);
                            //isShipPlaced = false;
                            } else if (dir == 1) {//dir = left
                                //Check if all pos are available and assign
                                isShipPlaced = AssignLeft(xInitPos, _ship, yInitPos, isShipPlaced);
                            //isShipPlaced = false;
                            } else if (dir == 2) {// dir = up
                                //Check if all pos are available and assign
                                isShipPlaced = AssignUp(yInitPos, _ship, xInitPos, isShipPlaced);
                            //isShipPlaced = false;
                            }
                        }
                    } while ((_dirs[0] != 0 || _dirs[1] != 0 || _dirs[2] != 0 || _dirs[3] != 0) && (!isShipPlaced));
                }
            } while (!isShipPlaced);// & _randTry <= 1200000); //in 5000 tries
        // PrintGrid();
        //System.out.println("\n------------------\n");
        }//for each ship
    }

    /**
     *  Place the Ship on a Downward position, Try if it can than place
     * @param yInitPos: initial y positon at rand
     * @param _ship: the ships itself
     * @param xInitPos: initial x position at rand
     * @param isShipPlaced: indicates if all positons have been hit
     * @return: if can assign return true.
     */
    private boolean AssignDown(Integer yInitPos, Ship _ship, Integer xInitPos, boolean isShipPlaced) {
        for (Integer y = yInitPos + 1; y <= yInitPos + _ship.Positions.size() - 1; y++) {
            if (!IsPositionAvailable(xInitPos, y)) {
                return false;
            }
        }
        isShipPlaced = true;
        if (isShipPlaced) {
            //assign values
            Integer idx = _ships.indexOf(_ship); //assign initial position
            _grid.SetValue(xInitPos, yInitPos, idx.toString());
            int posIdx = 0;
            _ship.Positions.get(posIdx).XValue = xInitPos;
            _ship.Positions.get(posIdx).YValue = yInitPos;
            _ship.Positions.get(posIdx).HitPointValue = "1";
            //assign the rest
            for (Integer y = yInitPos + 1; y <= yInitPos + _ship.Positions.size() - 1; y++) {
                _grid.SetValue(xInitPos, y, idx.toString());
                posIdx++;
                _ship.Positions.get(posIdx).XValue = xInitPos;
                _ship.Positions.get(posIdx).YValue = y;
                _ship.Positions.get(posIdx).HitPointValue = "1";
            }
        }
        return isShipPlaced;
    }

    /**
     *  Place the Ship on a leftward position, Try if it can than place
     * @param yInitPos: initial y positon at rand
     * @param _ship: the ships itself
     * @param xInitPos: initial x position at rand
     * @param isShipPlaced: indicates if all positons have been hit
     * @return: if can assign return true.
     */
    private boolean AssignLeft(Integer xInitPos, Ship _ship, Integer yInitPos, boolean isShipPlaced) {
        //dir = left
        //Check if all pos are available
        for (Integer x = xInitPos - 1; x >= xInitPos - (_ship.Positions.size() - 1); x--) {
            if (!IsPositionAvailable(x, yInitPos)) {
                return false;
            }
        }
        isShipPlaced = true;
        if (isShipPlaced) {
            //assign values
            Integer idx = _ships.indexOf(_ship); //assign initial position
            _grid.SetValue(xInitPos, yInitPos, idx.toString());
            int posIdx = 0;
            _ship.Positions.get(posIdx).XValue = xInitPos;
            _ship.Positions.get(posIdx).YValue = yInitPos;
            _ship.Positions.get(posIdx).HitPointValue = "1";
            //assign the rest
            for (Integer x = xInitPos - 1; x >= xInitPos - (_ship.Positions.size() - 1); x--) {
                _grid.SetValue(x, yInitPos, idx.toString());
                posIdx++;
                _ship.Positions.get(posIdx).XValue = x;
                _ship.Positions.get(posIdx).YValue = yInitPos;
                _ship.Positions.get(posIdx).HitPointValue = "1";
            }
        }
        return isShipPlaced;
    }

    /**
     *  Place the Ship on a Rightward position, Try if it can than place
     * @param yInitPos: initial y positon at rand
     * @param _ship: the ships itself
     * @param xInitPos: initial x position at rand
     * @param isShipPlaced: indicates if all positons have been hit
     * @return: if can assign return true.
     */
    private boolean AssignRight(Integer xInitPos, Ship _ship, Integer yInitPos, boolean isShipPlaced) {
        //dir = right
        //Check if all pos are available
        for (Integer x = xInitPos + 1; x <= xInitPos + _ship.Positions.size() - 1; x++) {
            if (!IsPositionAvailable(x, yInitPos)) {
                return false;
            }
        }
        isShipPlaced = true;
        if (isShipPlaced) {
            //assign values
            Integer idx = _ships.indexOf(_ship); //assign initial position
            _grid.SetValue(xInitPos, yInitPos, idx.toString());
            int posIdx = 0;
            _ship.Positions.get(posIdx).XValue = xInitPos;
            _ship.Positions.get(posIdx).YValue = yInitPos;
            _ship.Positions.get(posIdx).HitPointValue = "1";
            //assign the rest
            for (Integer x = xInitPos + 1; x <= xInitPos + _ship.Positions.size() - 1; x++) {
                _grid.SetValue(x, yInitPos, idx.toString());
                posIdx++;
                _ship.Positions.get(posIdx).XValue = x;
                _ship.Positions.get(posIdx).YValue = yInitPos;
                _ship.Positions.get(posIdx).HitPointValue = "1";
            }
        }
        return isShipPlaced;
    }

    /**
     *  Place the Ship on a Upward position, Try if it can than place
     * @param yInitPos: initial y positon at rand
     * @param _ship: the ships itself
     * @param xInitPos: initial x position at rand
     * @param isShipPlaced: indicates if all positons have been hit
     * @return: if can assign return true.
     */
    private boolean AssignUp(Integer yInitPos, Ship _ship, Integer xInitPos, boolean isShipPlaced) {
        // dir = up
        //Check if all pos are available
        for (Integer y = yInitPos - 1; y >= yInitPos - (_ship.Positions.size() - 1); y--) {
            if (!IsPositionAvailable(xInitPos, y)) {
                return false;
            }
        }
        isShipPlaced = true;
        if (isShipPlaced) {
            //assign values
            Integer idx = _ships.indexOf(_ship); //assign initial position
            _grid.SetValue(xInitPos, yInitPos, idx.toString());
            int posIdx = 0;
            _ship.Positions.get(posIdx).XValue = xInitPos;
            _ship.Positions.get(posIdx).YValue = yInitPos;
            _ship.Positions.get(posIdx).HitPointValue = "1";
            //assign the rest
            for (Integer y = yInitPos - 1; y >= yInitPos - (_ship.Positions.size() - 1); y--) {
                _grid.SetValue(xInitPos, y, idx.toString());
                posIdx++;
                _ship.Positions.get(posIdx).XValue = xInitPos;
                _ship.Positions.get(posIdx).YValue = y;
                _ship.Positions.get(posIdx).HitPointValue = "1";
            }
        }
        return isShipPlaced;
    }

    /**
     * Check is that positon is not take or out bounds on the grid
     * @param xPos: x position
     * @param yPos: y positon
     * @return: true or false based on Availability
     */
    private boolean IsPositionAvailable(Integer xPos, Integer yPos) {
        try {
            if (_grid.GetValue(xPos, yPos).equals("~")) {
                return true;
            } else {
                return false;
            }
        } catch (Exception ex) {
            return false;
        }
    }

    /**
     * It only checks if Position is out of bounds
     * @param xPos: x position
     * @param yPos: y position
     * @return: true or false based on Availability
     */
    public boolean IsPositionValid(Integer xPos, Integer yPos) {
        try {
            if (_grid.GetValue(xPos, yPos) != null) {
                return true;
            } else {
                return false;
            }
        } catch (Exception ex) {
            return false;
        }
    }

    /**
     * Play the game, take inputs and call other methods
     */
    public void PlayGame() {
        boolean _areShipsDead = false;
        String _input = "";
        do {
            System.out.println("Fire your shot i.e. A1, B6...");
            //Take input
            _input = _kb.next();
            ProcessHit(_input);
            _areShipsDead = EvaluateGame();
        } while (!_areShipsDead && _grid.GetMissHits() < 15);
        if (_areShipsDead) {
            PrintGrid();
            //PrintGrid4Game(_ships);
            PrintGridEndGame(_ships);
            System.out.println("Congratulation you win......!");
        } else if (_grid.GetMissHits() >= 15) {
            PrintGrid();
            //PrintGrid4Game(_ships);
            PrintGridEndGame(_ships);
            System.out.println("Sorry, you lose... over 15 tries.");
        }
    }

    /**
     * Process the hit fired by the user
     * @param input: is the users keyboard input
     */
    private void ProcessHit(String input) {
        Integer y = ((int) input.toLowerCase().charAt(0) - 96);
        Integer x = (Integer.parseInt(input.toLowerCase().substring(1)));

        x--;//index based
        y--; //index based
        if (IsPositionAvailable(x, y)) {//Miss
            _grid.SetValue(x, y, ".");
            //PrintGrid();
            PrintGrid4Game(_ships);
            System.out.println("You missed, try again");
        } else if (!IsPositionValid(x, y) || x > _grid.XSize || y > _grid.YSize) {
            //PrintGrid();
            PrintGrid4Game(_ships);
            System.out.println("Invalid Entry, try again");
        } else if (_grid.GetValue(x, y).equals(".")) {
            //PrintGrid();
            PrintGrid4Game(_ships);
            System.out.println("Sorry already a missed hit, try again");
        } else// has ship index
        {
            Integer shipIdx = Integer.parseInt(_grid.GetValue(x, y));
            for (Position _pos : _ships.get(shipIdx).Positions) {
                if (_pos.XValue == x && _pos.YValue == y) {
                    if (_pos.HitPointValue.equals("X")) {
                        //PrintGrid();
                        PrintGrid4Game(_ships);
                        System.out.println("Ship has already been hit, try again");
                    } else {
                        _pos.HitPointValue = "X";
                        //PrintGrid();
                        PrintGrid4Game(_ships);
                        System.out.println("Congrats, you hit a ship");
                    }
                    break;
                }
            }
        }
    }

    /**
     * Check for conditions for Game Over and Sunk ships
     * @return: If false game over
     */
    public boolean EvaluateGame() {
        boolean isGameOver = true;
        for (Ship _ship : _ships) {
            boolean isShipDead = true;
            for (Position _pos : _ship.Positions) {
                if (!_pos.HitPointValue.equals("X") && !_pos.HitPointValue.equals("*")) {
                    isShipDead = false;
                }
            }
            if (isShipDead) {
                for (Position _pos : _ship.Positions) {
                    _pos.HitPointValue = "*";
                }
                if (_ship.ShipStatus != Status.dead) {
                    System.out.println("You sunk a " + _ship.toString());
                    _ship.ShipStatus = Status.dead;
                }
            } else {
                isGameOver = false;
            }
        }
        return isGameOver;
    }
}



